#include <am.h>
#include <klib.h>
#include <klib-macros.h>
#include <stdarg.h>

#if !defined(__ISA_NATIVE__) || defined(__NATIVE_USE_KLIB__)

#define STRLEN(str) (sizeof(str) / sizeof(char) - 1)
static const char INT_MIN_STR[] = "2147483648";
static inline int is_digital(char c) { return c >= '0' && c <= '9'; }

#define IO_OUT(ch) putch(ch)

static inline int fmt_out_binary(unsigned int unumber, const char ph, int ph_c, char *const buffer)
{
  int count = 0, n = 0;
  do
  {
    buffer[n++] = '0' + (unumber & 0b1);
    unumber >>= 1;
  } while (unumber);
  ph_c -= n;
  while (ph_c > 0)
  {
    putch(ph);
    count++;
    ph_c--;
  }
  while (n)
  {
    putch(buffer[--n]);
    count++;
  }
  return count;
}
static inline int fmt_buffer_binary(unsigned int unumber, const char ph, int ph_c, char *const buffer, char **const ptr)
{
  int count = 0, n = 0;
  do
  {
    buffer[n++] = '0' + (unumber & 0b1);
    unumber >>= 1;
  } while (unumber);
  ph_c -= n;
  while (ph_c > 0)
  {
    **ptr = ph;
    (*ptr)++;
    count++;
    ph_c--;
  }
  while (n)
  {
    **ptr = buffer[--n];
    (*ptr)++;
    count++;
  }
  return count;
}
static inline int fmt_out_hexadecimal(unsigned int unumber, const char ph, int ph_c, char *const buffer)
{
  int count = 0, n = 0;
  unsigned int uquotient;
  do
  {
    uquotient = unumber & 0xf;
    buffer[n++] = uquotient + (uquotient < 0xa ? '0' : ('a' - 0xa));
    unumber >>= 4;
  } while (unumber);
  ph_c -= n;
  while (ph_c > 0)
  {
    putch(ph);
    count++;
    ph_c--;
  }
  while (n)
  {
    putch(buffer[--n]);
    count++;
  }
  return count;
}
static inline int fmt_buffer_hexadecimal(unsigned int unumber, const char ph, int ph_c, char *const buffer, char **const ptr)
{
  int count = 0, n = 0;
  unsigned int uquotient;
  do
  {
    uquotient = unumber & 0xf;
    buffer[n++] = uquotient + (uquotient < 0xa ? '0' : ('a' - 0xa));
    unumber >>= 4;
  } while (unumber);
  ph_c -= n;
  while (ph_c > 0)
  {
    **ptr = ph;
    (*ptr)++;
    count++;
    ph_c--;
  }
  while (n)
  {
    **ptr = (buffer[--n]);
    (*ptr)++;
    count++;
  }
  return count;
}
static inline int fmt_out_decimal(int number, const char ph, int ph_c, char *const buffer)
{
  bool is_negative = false;
  int count = 0, n = 0;
  if (number == 0x80000000)
  {
    ph_c -= STRLEN(INT_MIN_STR) + 1;
    if (ph == '0')
    {
      putch('-');
      count++;
    }
    while (ph_c > 0)
    {
      putch(ph);
      ph_c--;
      count++;
    }
    if (ph == ' ')
    { 
      putch('-');
      count++;
    }
    while (n < STRLEN(INT_MIN_STR))
    {
      putch(INT_MIN_STR[n++]);
      count++;
    }
    return count;
  }
  else if (number < 0)
  {
    number = -number;
    ph_c--;
    is_negative = true;
  }
  do
  {
    buffer[n++] = '0' + (number % 10);
    number /= 10;
  } while (number);
  ph_c -= n;
  if (is_negative && ph == '0')
  {
    putch('-');
    count++;
  }

  while (ph_c > 0)
  {
    putch(ph);
    count++;
    ph_c--;
  }
  if (is_negative && ph == ' ')
  {
    putch('-');
    count++;
  }
  while (n > 0)
  {
    putch(buffer[--n]);
    count++;
  }
  return count;
}

static inline int fmt_buffer_decimal(int number, const char ph, int ph_c, char *const buffer, char **const ptr)
{
  bool is_negative = false;
  int count = 0, n = 0;
  if (number == 0x80000000)
  {
    ph_c -= STRLEN(INT_MIN_STR) + 1;
    if (ph == '0')
    {
      **ptr = '-';
      (*ptr)++;
      count++;
    }

    while (ph_c > 0)
    {
      **ptr = ph;
      (*ptr)++;
      ph_c--;
      count++;
    }
    if (ph == ' ')
    {
      **ptr = '-';
      (*ptr)++;
      count++;
    }
    while (n < STRLEN(INT_MIN_STR))
    {
      **ptr = INT_MIN_STR[n++];
      (*ptr)++;
      count++;
    }
    return count;
  }
  else if (number < 0)
  {
    number = -number;
    ph_c--;
    is_negative = true;
  }
  do
  {
    buffer[n++] = '0' + (number % 10);
    number /= 10;
  } while (number);
  ph_c -= n;
  if (is_negative && ph == '0')
  {
    **ptr = '-';
    (*ptr)++;
    count++;
  }

  while (ph_c > 0)
  {
    **ptr = ph;
    (*ptr)++;
    count++;
    ph_c--;
  }
  if (is_negative && ph == ' ')
  {
    **ptr = '-';
    (*ptr)++;
    count++;
  }
  while (n > 0)
  {
    **ptr = buffer[--n];
    (*ptr)++;
    count++;
  }
  return count;
}

static inline int fmt_out_string(const char *str, int ph_c, char *const buffer)
{
  int count = 0;
  ph_c -= strlen(str);
  while (ph_c > 0)
  {
    putch(' ');
    count++;
    ph_c--;
  }
  while (*str)
  {
    putch(*str++);
    count++;
  }
  return count;
}
static inline int fmt_buffer_string(const char *str, int ph_c, char *const buffer, char **const ptr)
{
  int count = 0;
  ph_c -= strlen(str);
  while (ph_c > 0)
  {
    **ptr = ' ';
    (*ptr)++;
    count++;
    ph_c--;
  }
  while (*str)
  {
    **ptr = *str++;
    (*ptr)++;
    count++;
  }
  return count;
}

int printf(const char *fmt, ...)
{
  va_list args;
  va_start(args, fmt);
  int count = 0;
  char *traverse = (char *)fmt;
  char buffer[64]; // init
  while (*traverse != '\0')
  {
    if (*traverse == '%')
    {
      traverse++;
      char ph;
      if (*traverse == '0')
      {
        ph = '0';
        traverse++;
      }
      else
      {
        ph = ' ';
      }
      int ph_c = 0;
      if (*traverse != '-')
      {
        while (is_digital(*traverse))
        {
          ph_c *= 10;
          ph_c += *traverse - '0';
          traverse++;
        }
      }
      else
      {
        do
        {
          traverse++;
        } while (is_digital(*traverse));
      }
      switch (*traverse)
      {
      case 'b':
        count += fmt_out_binary(va_arg(args, unsigned int), ph, ph_c, buffer);
        break;
      case 'd':
        count += fmt_out_decimal(va_arg(args, int), ph, ph_c, buffer);
        break;
      case 's':
        count += fmt_out_string(va_arg(args, const char *), ph_c, buffer);
        break;
      case 'x':
        count += fmt_out_hexadecimal(va_arg(args, unsigned int), ph, ph_c, buffer);
        break;
      case '%':
        putch('%');
        count++;
        break;
      }
      traverse++;
    }
    else
    {
      putch(*traverse++);
      count++;
    }
  }

  va_end(args);
  return count;
}

int vsprintf(char *out, const char *fmt, va_list ap)
{
  panic("Not implemented");
}

int sprintf(char *out, const char *fmt, ...)
{
  va_list args;
  va_start(args, fmt);
  int count = 0;
  char *traverse = (char *)fmt;
  char buffer[64]; // init
  while (*traverse != '\0')
  {
    if (*traverse == '%')
    {
      traverse++;
      char ph;
      if (*traverse == '0')
      {
        ph = '0';
        traverse++;
      }
      else
      {
        ph = ' ';
      }
      int ph_c = 0;
      if (*traverse != '-')
      {
        while (is_digital(*traverse))
        {
          ph_c *= 10;
          ph_c += *traverse - '0';
          traverse++;
        }
      }
      else
      {
        do
        {
          traverse++;
        } while (is_digital(*traverse));
      }
      switch (*traverse)
      {
      case 'b':
        count += fmt_buffer_binary(va_arg(args, unsigned int), ph, ph_c, buffer, &out);
        break;
      case 'd':
        count += fmt_buffer_decimal(va_arg(args, int), ph, ph_c, buffer, &out);
        break;
      case 's':
        count += fmt_buffer_string(va_arg(args, const char *), ph_c, buffer, &out);
        break;
      case 'x':
        count += fmt_buffer_hexadecimal(va_arg(args, unsigned int), ph, ph_c, buffer, &out);
        break;
      case '%':
        *out++ = '%';
        count++;
        break;
      }
      traverse++;
    }
    else
    {
      *out++ = *traverse++;
      count++;
    }
  }
  *out = '\0';
  count++;
  va_end(args);
  return count;
}

int snprintf(char *out, size_t n, const char *fmt, ...)
{
  panic("Not implemented");
}

int vsnprintf(char *out, size_t n, const char *fmt, va_list ap)
{
  panic("Not implemented");
}

#endif
